<template>
  <Form
    v-bind="getBindValue"
    :class="getFormClass"
    ref="formElRef"
    :model="formModel"
    @keypress.enter="handleEnterPress"
  >
    <div class="flex">
      <Row v-bind="getRow" style="flex: 1">
        <slot name="formHeader"></slot>
        <template v-for="schema in getSchema" :key="schema.field">
          <FormItem
            :isAdvanced="fieldsIsAdvancedMap[schema.field]"
            :tableAction="tableAction"
            :formActionType="formActionType"
            :schema="schema"
            :formProps="getProps"
            :allDefaultValues="defaultValueRef"
            :formModel="formModel"
            :setFormModel="setFormModel"
          >
            <template #[item]="data" v-for="item in Object.keys($slots)">
              <slot :name="item" v-bind="data || {}"></slot>
            </template>
          </FormItem>
        </template>
        <slot name="formFooter"></slot>
      </Row>
      <FormAction
        v-bind="getFormActionBindProps"
        @toggle-advanced="handleToggleAdvanced"
      >
        <template
          #[item]="data"
          v-for="item in [
            'resetBefore',
            'submitBefore',
            'advanceBefore',
            'advanceAfter',
          ]"
        >
          <slot :name="item" v-bind="data || {}"></slot>
        </template>
      </FormAction>
    </div>
  </Form>
</template>
<script lang="ts">
import type { FormActionType, FormProps, FormSchema } from './types/form';
import type { AdvanceState } from './types/hooks';
import type { Ref } from 'vue';
import {
  defineComponent,
  reactive,
  ref,
  computed,
  unref,
  onMounted,
  watch,
  nextTick,
} from 'vue';
import { Form, Row } from 'ant-design-vue';
import FormItem from './components/FormItem.vue';
import FormAction from './components/FormAction.vue';
import { useGo } from '@/hooks/web/usePage';

import { dateItemType } from './helper';
import { dateUtil } from '@/utils/dateUtil';

// import { cloneDeep } from 'lodash-es';
import { deepMerge } from '@/utils';

import { useFormValues } from './hooks/useFormValues';
import useAdvanced from './hooks/useAdvanced';
import { useFormEvents } from './hooks/useFormEvents';
import { createFormContext } from './hooks/useFormContext';
import { useAutoFocus } from './hooks/useAutoFocus';
import { useModalContext } from '@/components/Modal';
import { useDebounceFn } from '@vueuse/core';

import { basicProps } from './props';
import { useDesign } from '@/hooks/web/useDesign';
import { cloneDeep } from 'lodash-es';
export default defineComponent({
  name: 'BasicForm',
  components: { FormItem, Form, Row, FormAction },
  props: basicProps,
  emits: [
    'advanced-change',
    'reset',
    'submit',
    'register',
    'field-value-change',
  ],
  setup(props, { emit, attrs }) {
    const linkSchemas = ref<Nullable<FormSchema[]>>([]);
    const formModel = reactive<Recordable>({});
    const modalFn = useModalContext();
    const advanceState = reactive<AdvanceState>({
      isAdvanced: false,
      hideAdvanceBtn: false,
      isLoad: false,
      actionSpan: 6,
    });

    const defaultValueRef = ref<Recordable>({});
    const isInitedDefaultRef = ref(false);
    const propsRef = ref<Partial<FormProps>>({});
    const schemaRef = ref<Nullable<FormSchema[]>>(null);
    const formElRef = ref<Nullable<FormActionType>>(null);

    const { prefixCls } = useDesign('basic-form');

    // Get the basic configuration of the form
    const getProps = computed((): FormProps => {
      return { ...props, ...unref(propsRef) } as FormProps;
    });

    const getFormClass = computed(() => {
      return [
        prefixCls,
        {
          [`${prefixCls}--compact`]: unref(getProps).compact,
        },
        unref(getProps).mode === 'view' ? 'viewClass' : '',
      ];
    });

    // Get uniform row style and Row configuration for the entire form
    const getRow = computed((): Recordable => {
      const { baseRowStyle = {}, rowProps } = unref(getProps);
      return {
        style: baseRowStyle,
        ...rowProps,
      };
    });

    const getBindValue = computed(
      () => ({ ...attrs, ...props, ...unref(getProps) }) as Recordable,
    );

    const getSchema = computed((): FormSchema[] => {
      const schemas: FormSchema[] =
        unref(schemaRef) || (unref(getProps).schemas as any);
      const { auditFields, mode } = unref(getProps);
      
      for (const schema of schemas) {
        const {
          defaultValue,
          component,
          isHandleDateDefaultValue = true,
        } = schema;
        // handle date type
        if (
          isHandleDateDefaultValue &&
          defaultValue &&
          dateItemType.includes(component)
        ) {
          if (!Array.isArray(defaultValue)) {
            schema.defaultValue = dateUtil(defaultValue);
          } else {
            const def: any[] = [];
            defaultValue.forEach((item) => {
              def.push(dateUtil(item));
            });
            schema.defaultValue = def;
          }
        }
      }
      if (unref(getProps).showAdvancedButton) {
        return cloneDeep(
          schemas.filter(
            (schema) => schema.component !== 'Divider',
          ) as FormSchema[],
        );
      } else {
        return cloneDeep(schemas as FormSchema[]);
      }
    });

    const { handleToggleAdvanced, fieldsIsAdvancedMap } = useAdvanced({
      advanceState,
      emit,
      getProps,
      getSchema,
      formModel,
      defaultValueRef,
    });

    const { handleFormValues, initDefault } = useFormValues({
      getProps,
      defaultValueRef,
      getSchema,
      formModel,
    });

    useAutoFocus({
      getSchema,
      getProps,
      isInitedDefault: isInitedDefaultRef,
      formElRef: formElRef as Ref<FormActionType>,
    });

    const {
      handleSubmit,
      setFieldsValue,
      clearValidate,
      validate,
      validateFields,
      getFieldsValue,
      updateSchema,
      resetSchema,
      appendSchemaByField,
      removeSchemaByField,
      resetFields,
      scrollToField,
    } = useFormEvents({
      emit,
      getProps,
      formModel,
      getSchema,
      defaultValueRef,
      formElRef: formElRef as Ref<FormActionType>,
      schemaRef: schemaRef as Ref<FormSchema[]>,
      handleFormValues,
    });

    createFormContext({
      resetAction: resetFields,
      submitAction: handleSubmit,
    });

    watch(
      () => unref(getProps).model,
      () => {
        const { model } = unref(getProps);
        if (!model) return;
        setFieldsValue(model);
      },
      {
        immediate: true,
      },
    );

    watch(
      () => unref(getProps).schemas,
      (schemas) => {
        resetSchema(schemas ?? []);
      },
    );

    watch(
      () => getSchema.value,
      (schema) => {
        nextTick(() => {
          //  Solve the problem of modal adaptive height calculation when the form is placed in the modal
          modalFn?.redoModalHeight?.();
        });
        if (unref(isInitedDefaultRef)) {
          return;
        }
        if (schema?.length) {
          initDefault();
          isInitedDefaultRef.value = true;
        }
      },
    );

    watch(
      () => formModel,
      useDebounceFn(() => {
        unref(getProps).submitOnChange && handleSubmit();
      }, 300),
      { deep: true },
    );

    async function setProps(
      formProps: Partial<FormProps>,
      replace?: boolean,
    ): Promise<void> {
      // 是否直接替换
      if (replace) {
        propsRef.value = formProps;
      }
      propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
    }

    function setFormModel(key: string, value: any, schema: FormSchema) {
      formModel[key] = value;
      emit('field-value-change', key, value);
      // TODO 优化验证，这里如果是autoLink=false手动关联的情况下才会再次触发此函数
      if (schema && schema.itemProps && !schema.itemProps.autoLink) {
        validateFields([key]).catch((_) => {});
      }
    }

    function handleEnterPress(e: KeyboardEvent) {
      const { autoSubmitOnEnter } = unref(getProps);
      if (!autoSubmitOnEnter) return;
      if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
        const target: HTMLElement = e.target as HTMLElement;
        if (
          target &&
          target.tagName &&
          target.tagName.toUpperCase() == 'INPUT'
        ) {
          handleSubmit();
        }
      }
    }

    const formActionType: Partial<FormActionType> = {
      getFieldsValue,
      setFieldsValue,
      resetFields,
      updateSchema,
      resetSchema,
      setProps,
      removeSchemaByField,
      appendSchemaByField,
      clearValidate,
      validateFields,
      validate,
      submit: handleSubmit,
      scrollToField: scrollToField,
      getFormSchema: () => unref(getSchema),
    };
    onMounted(() => {
      emit('register', formActionType);
      initDefault();
    });

    return {
      getBindValue,
      handleToggleAdvanced,
      handleEnterPress,
      formModel,
      defaultValueRef,
      advanceState,
      getRow,
      getProps,
      formElRef,
      getSchema,
      formActionType: formActionType as any,
      setFormModel,
      getFormClass,
      getFormActionBindProps: computed(
        (): Recordable => ({ ...getProps.value, ...advanceState }),
      ),
      fieldsIsAdvancedMap,
      ...formActionType,
    };
  },
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-basic-form';

.@{prefix-cls} {
  .ant-form-item {
    &-label label::after {
      margin: 0 6px 0 2px;
    }

    &-with-help {
      margin-bottom: 0;
    }

    &:not(.ant-form-item-with-help) {
      margin-bottom: 20px;
    }

    &.suffix-item {
      .ant-form-item-children {
        display: flex;
      }

      .ant-form-item-control {
        margin-top: 4px;
      }

      .suffix {
        display: inline-flex;
        padding-left: 6px;
        margin-top: 1px;
        line-height: 1;
        align-items: center;
      }
    }
  }

  .ant-form-explain {
    font-size: 14px;
  }

  &--compact {
    .ant-form-item {
      margin-bottom: 8px !important;
    }
  }
}
.ant-input-affix-wrapper-disabled,
.ant-input[disabled],
.ant-input-number-input,
.ant-select-disabled.ant-select:not(.ant-select-customize-input)
  .ant-select-selector,
input[disabled] {
  color: rgba(0, 0, 0, 0.88) !important;
}
.viewClass {
  .ant-input,
  .ant-input-affix-wrapper,
  .ant-select:not(.ant-select-customize-input) .ant-select-selector,
  .ant-input-number,
  .ant-input-group-addon,
  .ant-picker {
    border: 0 !important;
  }
  .ant-picker-input ::placeholder {
    color: transparent !important;
  }
  .ant-input-suffix,
  .ant-select-arrow,
  .ant-picker-suffix,
  .ant-input-group-addon:last-child {
    display: none !important;
  }

  .ant-input-affix-wrapper-disabled,
  .ant-input[disabled],
  .ant-input-number-input,
  .ant-select-disabled.ant-select:not(.ant-select-customize-input)
    .ant-select-selector,
  .ant-picker.ant-picker-disabled {
    background: #fff !important;
  }
  .ant-select-disabled.ant-select-multiple .ant-select-selection-item {
    border: none;
    background: #fff;
    padding: 0;
  }

  .ant-input.ant-input-disabled {
    resize: none !important;
  }
  .ant-form-item-explain.ant-form-item-explain-connected {
    display: none;
  }

  .ant-select {
    cursor: none !important;
  }
  .ant-select-disabled.ant-select:not(.ant-select-customize-input)
    .ant-select-selector {
    cursor: default;
  }
  .ant-select-disabled.ant-select-multiple
    .ant-select-selection-overflow
    .ant-select-selection-overflow-item:not(:first-child) {
    .ant-select-selection-item::before {
      content: '，';
    }
  }
  .ant-select-selection-item {
    flex: none !important;
  }
  .ant-select-selection-search {
    display: none !important;
  }
  .ant-select-single .ant-select-selector .ant-select-selection-item {
    user-select: unset !important;
    cursor: pointer;
  }
  .ant-select-single .ant-select-selector::after,
  .ant-select-single .ant-select-selector .ant-select-selection-item::after,
  .ant-select-single
    .ant-select-selector
    .ant-select-selection-placeholder::after {
    display: inline-block;
    width: 0;
    visibility: none !important;
    content: '';
  }
}
</style>
